package com.sig.sigandroidDisconnected;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.content.res.AssetManager;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.Matrix;
import android.util.Log;

public abstract class ES20AbstractRenderer implements Renderer {

	public static String TAG = "ES20AbstractRenderer";

	//Class level or global variables are usually
	//unhealthy. Try to minimize them!!

	//The target matrix that holds the end result
	//of all model transformations
	private float[] mCurrentModelMatrix = new float[16];

	//A matrix that is a result of setting the camera/eye
	private float[] mVMatrix = new float[16];

	//A matrix that is the result of setting the frustum
	private float[] mProjMatrix = new float[16];

	//A matrix that is a multiple of current model, view,
	//and projection matrices.
	private float[] mMVPMatrix = new float[16];

	//GLSL program object with both the shaders compiled,
	//linked, and attached.
	private int mProgram;

	//A handle for the uniform variable identifying the MVP matrix
	private int muMVPMatrixHandle;

	//An attribute handle in the vertex shader
	//for passing the vertex arrays.
	private int maPositionHandle;

	//Name of the default vertex shader
	//source code file in the asset directory.
	private static final String DEF_VERTEX_SHADER_FILENAME
	= "def_vertex_shader.txt";

	//Name of the default fragment shader
	//source code file in the asset directory.
	private static final String DEF_FRAGMENT_SHADER_FILENAME
	= "def_fragment_shader.txt";



	public ES20AbstractRenderer() {
		initializeMatrices();
	}


	@Override
	public void onDrawFrame(GL10 gl) {
		Log.d(TAG,"set look at matrix: view matrix");
		Matrix.setLookAtM(mVMatrix, 0,  0f,0f,-5f,  0f,0f,0f,  0f,1.0f,0.0f);

		GLES20.glClearColor(0.0f, 0.2f, 0.6f, 0.5f);
		GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT
				| GLES20.GL_COLOR_BUFFER_BIT);

		GLES20.glUseProgram(mProgram);
		checkGlError("glUseProgram");

		//Allow a derived class to set up drawing
		//for further down the chain.
		//the default doesn't do anything.
		preDraw(gl,this.maPositionHandle);

		//Real abstract method
		draw(gl,this.maPositionHandle);

	}

	@Override
	public void onSurfaceChanged(GL10 gl, int w, int h) {
		Log.d(TAG,"surface changed. Setting matrix frustum: projection matrix");
		GLES20.glViewport(0, 0, w, h);
		float ratio = (float) w / h;
		FrustumDimensions fd = this.getFrustumDimensions();
		Matrix.frustumM(mProjMatrix, 0, ratio * fd.bottom, ratio * fd.top,
				fd.bottom, fd.top, fd.near, fd.far);

	}

	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
		prepareSurface(gl,eglConfig);

	}

	public void initializeMatrices()
	{
		//Set the model matrix to identity
		//Subsequent scaling, rotation, etc will update this
		//in a stateful manner. So starting state matters.
		Matrix.setIdentityM(this.mCurrentModelMatrix, 0);

		//Although we use this matrix only once,
		//it is good to start with a known state.
		Matrix.setIdentityM(mMVPMatrix, 0);
	}


	public void prepareSurface(GL10 gl, EGLConfig eglConfig){
		Log.d(TAG,"preparing surface");
		mProgram = createProgram(
				this.getVertexShaderCodeString(),
				this.getFragmentShaderCodeString());
		if (mProgram == 0) {
			return;
		}
		Log.d(TAG,"Getting position handle:aPosition");
		maPositionHandle = getAttributeHandle("aPosition", "Getting Position Handle");

		Log.d(TAG,"Getting matrix handle:uMVPMatrix");
		muMVPMatrixHandle = getUniformHandle("uMVPMatrix",
				"Getting MVP uniform matrix handle");
	}

	protected FrustumDimensions getFrustumDimensions()
	{
		//Get default dimensions in this base class
		return FrustumDimensions.getDefault();
	}

	/**
	 * 1. Load vertex shader
	 * 2. load fragment shader
	 * 3. create program
	 * 4. attach shaders
	 * 5. link program and return it
	 * 6. returns 0 if the program cannot be created
	 */


	private int createProgram(String vertexSource, String fragmentSource)
	{
		int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
		if (vertexShader == 0) {
			return 0;
		}
		Log.d(TAG,"vertex shader created");

		int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
		if (pixelShader == 0) {
			return 0;
		}
		Log.d(TAG,"fragment shader created");

		int program = GLES20.glCreateProgram();
		if (program == 0) {
			checkGlError("Error Creating the program");
			return 0;
		}
		Log.d(TAG,"program created");
		GLES20.glAttachShader(program, vertexShader);
		checkGlError("glAttachShader");

		GLES20.glAttachShader(program, pixelShader);
		checkGlError("glAttachShader");

		GLES20.glLinkProgram(program);
		int[] linkStatus = new int[1];
		GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
		if (linkStatus[0] == GLES20.GL_TRUE) {
			Log.d(TAG,"Program successfully linked");
			return program;
		}

		Log.e(TAG, "Could not link program: ");
		Log.e(TAG, GLES20.glGetProgramInfoLog(program));
		GLES20.glDeleteProgram(program);
		return 0;
	}
	// Load a given type of shader and check for any errors
	private int loadShader(int shaderType, String source)
	{
		int shader = GLES20.glCreateShader(shaderType);
		if (shader == 0){
			checkGlError("Cannot create shader:"
					+ getShaderTypeAsString(shaderType));
			return 0;
		}
		//Associaate shader id to source
		GLES20.glShaderSource(shader, source);
		//Compile source
		GLES20.glCompileShader(shader);
		//Check if there is an error
		int[] compiled = new int[1];
		GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);

		//Return if there is no error
		if (compiled[0] == GLES20.GL_TRUE) {
			Log.d(TAG, getShaderTypeAsString(shaderType)
					+ " successfully compiled");
			return shader;
		}

		//report error if there is one and return 0
		Log.e(TAG, "Could not compile shader "
				+ getShaderTypeAsString(shaderType));
		Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
		GLES20.glDeleteShader(shader);
		return 0;
	}


	//Purely used for debugging purposes
	public String getShaderTypeAsString(int shaderType)
	{
		if (shaderType == GLES20.GL_VERTEX_SHADER){
			return "Vertex Shader";
		}
		else if (shaderType == GLES20.GL_FRAGMENT_SHADER) {
			return "Fragment Shader";
		}
		else { return new String("Unknown Shader Type Value");}
	}
	//Use this method to check and log GL errors
	protected void checkGlError(String op) {
		int error;
		while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
			Log.e(TAG, op + ": glError " + error);
			throw new RuntimeException(op + ": glError " + error);
		}
	}

	/*
	 * The following three methods update the mCurrentModelMatrix
	 * with the given model transformation.
	 * These are stateful accumulative methods.
	 */
	public void translate(float x, float y, float z)
	{
		float[] tempModelMatrix = new float[16];
		Matrix.setIdentityM(tempModelMatrix, 0);
		Matrix.translateM(tempModelMatrix,0,x,y,z);
		Matrix.multiplyMM(this.mCurrentModelMatrix, 0,
				tempModelMatrix, 0, this.mCurrentModelMatrix, 0);
	}
	public void rotate(float angle, float x, float y, float z)
	{
		float[] tempModelMatrix = new float[16];
		Matrix.setIdentityM(tempModelMatrix, 0);
		Matrix.rotateM(tempModelMatrix,0,angle,x,y,z);
		Matrix.multiplyMM(this.mCurrentModelMatrix, 0,
				tempModelMatrix, 0, this.mCurrentModelMatrix, 0);
	}
	public void scale(float xFactor, float yFactor, float zFactor)
	{
		float[] tempModelMatrix = new float[16];
		Matrix.setIdentityM(tempModelMatrix, 0);
		Matrix.scaleM(tempModelMatrix,0,xFactor,yFactor,zFactor);
		Matrix.multiplyMM(this.mCurrentModelMatrix, 0,
				tempModelMatrix, 0, this.mCurrentModelMatrix, 0);
	}

	/*
	 * Calculaute the final model view matrix
	 * 1. Order of matrix multiplication is important
	 * 2. MVPmatrix = proj * view * model;
	 * 3. Setup the MVP matrix in the vertex shader memory
	 */
	protected void setupMatrices()
	{
		float[] tempModelMatrix = new float[16];
		Matrix.setIdentityM(tempModelMatrix, 0);

		//translate the model combo next
		Matrix.multiplyMM(mMVPMatrix, 0, //matrix and offset
				mCurrentModelMatrix, 0,
				tempModelMatrix, 0);

		//translate eye coordinates first
		Matrix.multiplyMM(mMVPMatrix, 0,
				this.mVMatrix, 0,
				mMVPMatrix, 0);

		//Project it: screen coordinates
		Matrix.multiplyMM(mMVPMatrix, 0,
				mProjMatrix, 0,
				mMVPMatrix, 0);

		//Set the vertex uniform handler representing the MVP matrix
		GLES20.glUniformMatrix4fv(muMVPMatrixHandle, //uniform handle
				1, //number of uniforms. 1 if it is not an array
				false, //transpose: must be false
				mMVPMatrix, //client matrix memory pointer
				0); //offset
	}

	//Override this method to continue the onDrawframe callback
	//from the renderer.
	protected abstract void draw(GL10 gl, int positionHandle);

	//Override this to implement preDraw
	//useful for derived classes to specialize pre-draws
	protected void preDraw(GL10 gl, int positionHandle)
	{
		//nothing to do for this class: No op
	}

	//Use this method if your intent is to return
	//a default vertex shader.
	public String getDefaultVertexShaderCodeString()
	{
		return this.getStringFromAssetFile(DEF_VERTEX_SHADER_FILENAME);
	}

	//Use this method if your intent is to return
	//a default fragment shader.
	public String getDefaultFragmentShaderCodeString()
	{
		return this.getStringFromAssetFile(DEF_FRAGMENT_SHADER_FILENAME);
	}

	//Override this method if you want to provide
	//a different vertex shader program.
	protected String getVertexShaderCodeString()
	{
		String vertexShader =
				"uniform mat4 uMVPMatrix;\n" +
						"attribute vec4 aPosition;\n" +
						"void main() {\n" +
						" gl_Position = uMVPMatrix * aPosition;\n" +
						"}\n";
		return vertexShader;
	}

	//Override this method if you want to provide
	//a different vertex shader program.
	//In a derived method call getStringFromAssetFile(filename)
	//to read as string to be returned from here.
	protected String getFragmentShaderCodeString()
	{
		String fragmentShader =
				"void main() {\n" +
						" gl_FragColor = vec4(0.5, 0.25, 0.5, 1.0);\n" +
						"}\n";
		return fragmentShader;
	}
	//How to to read a text file from an asset
	//directory. In this approach you will need to
	//create your application object and provide a static
	//variable to create the context.
	//See MyApplication implementation to see how this works.
	//Or see http://androidbook.com/item/4224
	public String getStringFromAssetFile(String filename)
	{
		Context ctx = MyApplication.m_appContext;
		if ( ctx == null) {
			throw new RuntimeException("Sorry your app context is null");
		}
		try
		{
			AssetManager am = ctx.getAssets();
			InputStream is = am.open(filename);
			String s = convertStreamToString(is);
			is.close();
			return s;
		}
		catch (IOException x)
		{
			throw new RuntimeException("Sorry not able to read filename:" + filename,x);
		}
	}

	//Converting a file stream to a string
	//Optimize as you see fit. This may not be an efficient read
	private String convertStreamToString(InputStream is)
			throws IOException
			{
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		int i = is.read();
		while (i != -1)
		{
			baos.write(i);
			i = is.read();
		}
		return baos.toString();
			}

	//Use this if you need to use a program object
	//directly. Make sure you call this after the
	//surface is created and prepared in this base class.
	//otherwise it will be null.
	public int getGLSProgramObjectReference()
	{
		return this.mProgram;
	}
	//Use this method to get a handle to any
	//named attribute. It is a utility method.
	//It uses the program object that is in effect.
	//Ensure program object is valid before calling this.
	public int getAttributeHandle(String GLSLAttributeName, String comment)
	{
		String logComment = comment + ":" + GLSLAttributeName;
		Log.d(TAG,comment);
		int attributeHandle =
				GLES20.glGetAttribLocation(mProgram, GLSLAttributeName);
		checkGlError(logComment);
		if (attributeHandle == -1) {
			throw new RuntimeException(logComment);
		}
		return attributeHandle;
	}
	public int getUniformHandle(String GLSLUniformName, String comment)
	{
		String logComment = comment + ":" + GLSLUniformName;
		Log.d(TAG,comment);
		int uniformHandle =
				GLES20.glGetUniformLocation(mProgram, GLSLUniformName);
		checkGlError(logComment);
		if (uniformHandle == -1) {
			throw new RuntimeException(logComment);
		}
		return uniformHandle;
	}//eof-method
}
